package msu.ml.core;

import msu.ml.data.level2.*;

public class PreProcessor
{
   private int minRange = 0;
   private int maxRange = 0;

   public PreProcessor()
   {

   }

   public Scan process(Scan scan)
   {
      cullCloseData(scan);
      if(this.maxRange != 0)
         cullFarData(scan);
      smoothRangeFolded(scan);
      smoothOutliers(scan);
      return scan;
   }

   private void cullCloseData(Scan scan)
   {
      for(int j = 0; j < scan.numRays(); j++)
      {
         Ray ray = scan.getRay(j);
         if(scan.isReflectivityScan())
         {
            cullCloseData(new ReflectivityDataProvider(ray));
         }
         else if(scan.isDopplerScan())
         {
            cullCloseData(new VelocityDataProvider(ray));
            cullCloseData(new SpectrumWidthDataProvider(ray));
         }
      }
   }

   private void cullCloseData(IRayDataProvider provider)
   {
      double [] data = provider.getData();
      for(int i = 0; i < data.length; i++)
         if(i * provider.getGateSize() < minRange)
            provider.setDatum(i, Ray.EMPTY);
   }

   private void cullFarData(Scan scan)
   {
      for(int j = 0; j < scan.numRays(); j++)
      {
         Ray ray = scan.getRay(j);
         if(scan.isReflectivityScan())
         {
            cullFarData(new ReflectivityDataProvider(ray));
         }
         else if(scan.isDopplerScan())
         {
            cullFarData(new VelocityDataProvider(ray));
            cullFarData(new SpectrumWidthDataProvider(ray));
         }
      }
   }

   private void cullFarData(IRayDataProvider provider)
   {
      double [] data = provider.getData();
      for(int i = 0; i < data.length; i++)
         if(i * provider.getGateSize() > maxRange)
            provider.setDatum(i, Ray.EMPTY);
   }

   private void smoothRangeFolded(Scan scan)
   {
      for(int j = 0; j < scan.numRays(); j++)
      {
         Ray ray = scan.getRay(j);
         if(scan.isReflectivityScan())
         {
            smoothRangeFolded(scan, new ReflectivityDataProvider(ray), j);
         }
         else if(scan.isDopplerScan())
         {
            smoothRangeFolded(scan, new VelocityDataProvider(ray), j);
            smoothRangeFolded(scan, new SpectrumWidthDataProvider(ray), j);
         }
      }
   }

   private void smoothRangeFolded(Scan scan, IRayDataProvider provider, int rayIndex)
   {
      double [] data = provider.getData();
      for(int i = 0; i < data.length; i++)
         if(data[i] == Ray.RFVAL)
            provider.setDatum(i, getLocalizedAverage(scan, provider, rayIndex, i));
   }

   private void smoothOutliers(Scan scan)
   {
      if(scan.isReflectivityScan())
      {
         smoothOutliers(scan, new ReflectivityDataProvider(null));
      }
      else if(scan.isDopplerScan())
      {
         smoothOutliers(scan, new VelocityDataProvider(null));
         smoothOutliers(scan, new SpectrumWidthDataProvider(null));
      }
   }

   private void smoothOutliers(Scan scan, IRayDataProvider provider)
   {
      int n = 0;
      double sum = 0;
      double avg = 0;
      double std = 0;

      for(int j = 0; j < scan.numRays(); j++)
      {
         double [] data = provider.createForRay(scan.getRay(j)).getData();
         for(int i = 0; i < data.length; i++)
         {
            if(data[i] != Ray.EMPTY)
            {
               sum += data[i];
               n++;
            }
         }
      }

      avg = sum / (double)n;

      for(int j = 0; j < scan.numRays(); j++)
      {
         double [] data = provider.createForRay(scan.getRay(j)).getData();
         for(int i = 0; i < data.length; i++)
            if(data[i] != Ray.EMPTY)
               std += ((data[i] - avg) * (data[i] - avg));
      }

      std = Math.sqrt(std / (double)n);

      for(int j = 0; j < scan.numRays(); j++)
      {
         IRayDataProvider current = provider.createForRay(scan.getRay(j));
         double [] data = current.getData();
         for(int i = 0; i < data.length; i++)
            if(data[i] != Ray.EMPTY && (Math.abs(data[i] - avg) > (2 * std)))
               current.setDatum(i, getLocalizedAverage(scan, current, j, i)); 
      }

   }

   private float getLocalizedAverage(Scan scan, IRayDataProvider provider, int ray, int idx)
   {
      IRayDataProvider current = provider;
      IRayDataProvider prev = provider.createForRay(scan.getRay(ray > 0 ? ray - 1 : scan.numRays() - 1));
      IRayDataProvider next = provider.createForRay(scan.getRay(ray < scan.numRays() - 1 ? ray + 1 : 0));

      float sum = 0;
      float n = 0;

      for(int i = idx - 1; i < idx + 2; i++)
      {
         double value = prev.getDatum(i);
         if(value != Ray.EMPTY && value != Ray.RFVAL)
         {
            sum += value;
            n++;
         }
         value = current.getDatum(i);
         if(value != Ray.EMPTY && value != Ray.RFVAL && i != idx)
         {
            sum += value;
            n++;
         }
         value = next.getDatum(i);
         if(value != Ray.EMPTY && value != Ray.RFVAL)
         {
            sum += value;
            n++;
         }
      }

      if(n == 0)
         return Ray.EMPTY;
      else
         return sum / n;
   }

   public int getMaxRange() { return this.maxRange; }
   public void setMaxRange(int value) { this.maxRange = value; }
   public int getMinRange() { return this.minRange; }
   public void setMinRange(int value) { this.minRange = value; }
}
